using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Oops.Xna.Framework.Audio;
using Oops.Xna.Framework.Input;
using Oops.Xna.Framework.UI;

namespace Oops.Xna.Framework.GameState
{
	public enum GameScreenState
	{
		Transition,
		Active,
		Enter,
		Exit,
		Terminate
	}

	/// <summary>
	/// A game component that represents a screen in a game
	/// </summary>
	/// <remarks>
	/// A <see cref="GameScreen"/> can be considered a sub-game.  It has it's own 
	/// <see cref="Microsoft.Xna.Framework.IGameComponent"/> collection, <see cref="Microsoft.Xna.Framework.Content.ContentManager"/>,
	/// and service collection, just like the <see cref="Microsoft.Xna.Framework.Game"/> class.
	/// </remarks>
	public abstract class GameScreen2 : DrawableGameComponent
	{
		#region UpdateOrderComparer Declaration

		private class UpdateOrderComparer : IComparer<IUpdateable>
		{
			public static readonly UpdateOrderComparer Default = new UpdateOrderComparer();

			public int Compare(IUpdateable x, IUpdateable y)
			{
				// Null items are equal.
				if (x == null && y == null)
				{
					return 0;
				}

				// At least the first item exists.
				if (x != null)
				{
					if (y == null)
					{
						return -1;
					}

					if (x == y)
					{
						return 0;
					}

					if (x.UpdateOrder < y.UpdateOrder)
					{
						return -1;
					}
				}

				// Either the first item does not exist or the second item is updated later.
				return 1;
			}
		}

		#endregion

		#region DrawOrderComparer Declaration

		private class DrawOrderComparer : IComparer<IDrawable>
		{
			public static readonly DrawOrderComparer Default = new DrawOrderComparer();

			public int Compare(IDrawable x, IDrawable y)
			{
				// Null items are equal.
				if (x == null && y == null)
				{
					return 0;
				}

				// At least the first item exists.
				if (x != null)
				{
					if (y == null)
					{
						return -1;
					}

					if (x == y)
					{
						return 0;
					}

					if (x.DrawOrder < y.DrawOrder)
					{
						return -1;
					}
				}

				// Either the first item does not exist or the second item is drawn later.
				return 1;
			}
		}

		#endregion

		private string name;
		private GameComponentCollection gameComponents;

		private ContentManager content;

		private IGameStateService2 gameStateService;
		private IInputService inputService;
		private IAudioService audioService;
		private IScreenSpaceService screenSpaceService;

		private GameScreenServiceContainer services;

		private bool initialized;
		private List<IGameComponent> notYetInitialized = new List<IGameComponent>();
		private List<IUpdateable> updateableComponents = new List<IUpdateable>();
		private List<IDrawable> drawableComponents = new List<IDrawable>();

		private EventHandler updateOrderEventHandler;
		private EventHandler drawOrderEventHandler;

		/// <summary>
		/// Initializes a new instance of the <see cref="GameScreen"/> class.
		/// </summary>
		/// <param name="game">The associated <see cref="Microsoft.Xna.Framework.Game"/>.</param>
		/// <param name="name">The name of the <see cref="GameScreen"/>.</param>
		public GameScreen2(Game game, string name) : base(game)
		{
			// The name parameter must be specified.
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}

			// The game parameter must exist.
			if (game == null)
			{
				throw new ArgumentNullException("game");
			}

			// Hold onto parameters.
			this.name = name;

			// Create the game components collection and subscribe to some of the events.
			this.gameComponents = new GameComponentCollection();
			this.gameComponents.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs>(this.OnGameComponentAdded);
			this.gameComponents.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs>(this.OnGameComponentRemoved);

			// Create the service container for screen-specific services.
			this.services = new GameScreenServiceContainer(game.Services);

			// Create some event handlers.  This is just a garbage collection precaution.
			this.updateOrderEventHandler = new EventHandler(this.OnUpdateableUpdateOrderChanged);
			this.drawOrderEventHandler = new EventHandler(this.OnDrawableDrawOrderChanged);

			// Create the content manager.
			this.content = new ContentManager(this.services, this.Game.Content.RootDirectory);

			// Consider created screens new.
			this.IsNew = true;
		}

		/// <summary>
		/// Gets the name of this <see cref="GameScreen"/>.
		/// </summary>
		public string Name
		{
			get { return this.name; }
		}

		/// <summary>
		/// Gets if this <see cref="GameScreen"/> can ever be considered the top most.
		/// </summary>
		public virtual bool IsOverlay
		{
			get { return false; }
		}

		internal bool IsNew { get; set; }

		/// <summary>
		/// Gets the collection of <see cref="IGameComponent"/> instances owned by the <see cref="GameScreen"/>.
		/// </summary>
		public GameComponentCollection Components
		{
			get { return this.gameComponents; }
		}

		/// <summary>
		/// Gets the associated <see cref="Microsoft.Xna.Framework.Content.ContentManager"/> of the <see cref="GameScreen"/>.
		/// </summary>
		public ContentManager Content
		{
			get { return this.content; }
		}

		/// <summary>
		/// Gets the <see cref="IGameStateService"/> that owns the <see cref="GameScreen"/>.
		/// </summary>
		protected IGameStateService2 GameStateService
		{
			get { return this.gameStateService; }
		}

		/// <summary>
		/// Gets the <see cref="IInputService"/> in context for the <see cref="GameScreen"/>.
		/// </summary>
		protected IInputService InputService
		{
			get { return this.inputService; }
		}

		/// <summary>
		/// Gets the <see cref="IAudioService"/> in context for the <see cref="GameScreen"/>.
		/// </summary>
		protected IAudioService AudioService
		{
			get { return this.audioService; }
		}

		/// <summary>
		/// Gets the <see cref="IScreenSpaceService"/> in context for the <see cref="GameScreen"/>.
		/// </summary>
		protected IScreenSpaceService ScreenSpaceService
		{
			get { return this.screenSpaceService; }
		}

		/// <summary>
		/// Gets the GameServiceContainer holding all the service providers attached
		/// to the Game.
		/// </summary>
		public GameScreenServiceContainer Services
		{
			get { return this.services; }
		}

		/// <summary>
		/// The <see cref="GameScreen"/> is becoming active.
		/// </summary>
		/// <param name="gameTime">Time elapsed since the last call to <see cref="Microsoft.Xna.Framework.GameComponent.Update(Microsoft.Xna.Framework.GameTime)"/>.</param>
		/// <param name="initial"><b>true</b> if this the first time entering the screen. <b>false</b> otherwise.</param>
		internal protected virtual void Enter(GameTime gameTime, bool initial) { }

		/// <summary>
		/// The <see cref="GameScreen"/> is becoming inactive.
		/// </summary>
		/// <param name="gameTime">Time elapsed since the last call to <see cref="Microsoft.Xna.Framework.GameComponent.Update(Microsoft.Xna.Framework.GameTime)"/>.</param>
		internal protected virtual void Exit(GameTime gameTime) { }

		/// <summary>
		/// Determines this state of the <see cref="GameScreen"/>.
		/// </summary>
		/// <param name="gameTime">Time elapsed since the last call to <see cref="Microsoft.Xna.Framework.GameComponent.Update(Microsoft.Xna.Framework.GameTime)"/>.</param>
		/// <returns></returns>
		internal protected virtual GameScreenState GetTransition(GameTime gameTime)
		{
			// Always update.
			return GameScreenState.Active;
		}

		/// <summary>
		/// Loads any content that is valid only while this <see cref="GameScreen"/> is active.
		/// </summary>
		internal protected virtual void LoadDynamicContent() { }

		/// <summary>
		/// Unloads any content that is valid only while this <see cref="GameScreen"/> is active.
		/// </summary>
		internal protected virtual void UnloadDynamicContent() { }

		/// <summary>
		/// Called before Drawing takes place for this <see cref="GameScreen"/>.
		/// </summary>
		/// <param name="gameTime">Time elapsed since the last call to <see cref="Microsoft.Xna.Framework.GameComponent.Update(Microsoft.Xna.Framework.GameTime)"/>.</param>
		internal protected virtual void BeforeDraw(GameTime gameTime) { }

		#region GameComponent

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Dispose of all game components.
					for (int i = this.gameComponents.Count - 1; i >= 0; i--)
					{
						IDisposable disposable = this.gameComponents[i] as IDisposable;
						if (disposable != null)
						{
							disposable.Dispose();
						}
					}
				}
			}

			// Make sure to unload dynamic content as well.
			this.UnloadDynamicContent();

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent

		public override void Initialize()
		{
			// Get the required game services.
			this.gameStateService = this.Game.GetService<IGameStateService2>();
			this.inputService = this.Game.GetService<IInputService>();
			this.audioService = this.Game.GetService<IAudioService>();
			this.screenSpaceService = this.Game.GetService<IScreenSpaceService>();

			// Initialize all necessary game screens.
			while (this.notYetInitialized.Count != 0)
			{
				this.notYetInitialized[0].Initialize();
				this.notYetInitialized.RemoveAt(0);
			}

			// Call inherited method.
			base.Initialize();

			// Set the initialized flag.
			this.initialized = true;
		}

		#endregion

		#region IUpdateable

		public override void Update(GameTime gameTime)
		{
			// Update all enabled game components.
			for (int i = 0; i < this.updateableComponents.Count; i++)
			{
				IUpdateable updateable = this.updateableComponents[i];
				if (updateable.Enabled)
				{
					updateable.Update(gameTime);
				}
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion

		#region IDrawable

		public override void Draw(GameTime gameTime)
		{
			// Draw all visible game components.
			for (int i = 0; i < this.drawableComponents.Count; i++)
			{
				IDrawable drawable = this.drawableComponents[i];
				if (drawable.Visible)
				{
					drawable.Draw(gameTime);
				}
			}

			// Call inherited method.
			base.Draw(gameTime);
		}

		#endregion

		internal new void UnloadContent()
		{
			// Call the protected overloaded method.
			base.UnloadContent();
		}

		private void OnGameComponentAdded(object sender, GameComponentCollectionEventArgs e)
		{
			// Initialize or add the behavior to the initialization pending list.
			if (this.initialized)
			{
				e.GameComponent.Initialize();
			}
			else
			{
				this.notYetInitialized.Add(e.GameComponent);
			}

			// Check if the game component is updateable.  Almost has to be.
			IUpdateable updateable = e.GameComponent as IUpdateable;
			if (updateable != null)
			{
				// Determine where the game component should be inserted.
				int index = this.updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
				if (index < 0)
				{
					index = ~index;
					while ((index < this.updateableComponents.Count) && (this.updateableComponents[index].UpdateOrder == updateable.UpdateOrder))
					{
						index++;
					}
					this.updateableComponents.Insert(index, updateable);

					// Subscribe to some of the component's events.
					updateable.UpdateOrderChanged += this.updateOrderEventHandler;
				}
			}

			// Check if the game component is drawable.
			IDrawable drawable = e.GameComponent as IDrawable;
			if (drawable != null)
			{
				// Determine where the component should be inserted.
				int index = this.drawableComponents.BinarySearch(drawable, DrawOrderComparer.Default);
				if (index < 0)
				{
					index = ~index;
					while ((index < this.drawableComponents.Count) && (this.drawableComponents[index].DrawOrder == drawable.DrawOrder))
					{
						index++;
					}

					// Subscribe to some of the component's events.
					this.drawableComponents.Insert(index, drawable);
					drawable.DrawOrderChanged += this.drawOrderEventHandler;
				}
			}
		}

		private void OnGameComponentRemoved(object sender, GameComponentCollectionEventArgs e)
		{
			// Remove the behavior from the initialization pending list.
			if (!this.initialized)
			{
				this.notYetInitialized.Remove(e.GameComponent);
			}

			// Remove the game component from the updateable collection.
			IUpdateable updateable = e.GameComponent as IUpdateable;
			if (updateable != null)
			{
				this.updateableComponents.Remove(updateable);
				updateable.UpdateOrderChanged -= this.updateOrderEventHandler;
			}

			// Remove the game component from the drawable collection.
			IDrawable drawable = e.GameComponent as IDrawable;
			if (drawable != null)
			{
				this.drawableComponents.Remove(drawable);
				drawable.DrawOrderChanged -= this.drawOrderEventHandler;
			}
		}

		private void OnUpdateableUpdateOrderChanged(object sender, EventArgs e)
		{
			// Remove the component from the ordered list.
			IUpdateable updateable = sender as IUpdateable;
			this.updateableComponents.Remove(updateable);

			// Determine where the behavior should be inserted.
			int index = this.updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while (index < this.updateableComponents.Count && this.updateableComponents[index].UpdateOrder == updateable.UpdateOrder)
				{
					index++;
				}

				// Insert the component.
				this.updateableComponents.Insert(index, updateable);
			}
		}

		private void OnDrawableDrawOrderChanged(object sender, EventArgs e)
		{
			// Remove the component from the ordered list.
			IDrawable item = sender as IDrawable;
			this.drawableComponents.Remove(item);

			// Determine where the component should be inserted.
			int index = this.drawableComponents.BinarySearch(item, DrawOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while (index < this.drawableComponents.Count && this.drawableComponents[index].DrawOrder == item.DrawOrder)
				{
					index++;
				}

				// Insert the component.
				this.drawableComponents.Insert(index, item);
			}
		}
	}
}